🎯 Esercizi Pratici

Gestione degli Errori in Python - 10 Esercizi Progressivi

1
Calcolatrice Sicura
FACILE

Crea una calcolatrice che chieda all'utente due numeri e un'operazione (+, -, *, /) e gestisca tutti i possibili errori di input.

πŸ“‹ Requisiti:

  • Gestire input non numerici (ValueError)
  • Gestire divisione per zero (ZeroDivisionError)
  • Gestire operatori non validi
  • Fornire messaggi di errore chiari all'utente
  • Permettere all'utente di riprovare in caso di errore
# Struttura suggerita def calcolatrice(): while True: try: # Chiedi input utente # Esegui operazione pass except ValueError: # Gestisci input non numerico pass except ZeroDivisionError: # Gestisci divisione per zero pass

πŸ§ͺ Test Cases:

Input: 10, 5, +
Output: Risultato: 15.0
Input: 10, 0, /
Output: Errore: Impossibile dividere per zero
Input: abc, 5, +
Output: Errore: Input non numerico
2
Gestore di File di Configurazione
FACILE

Scrivi una funzione che legge un file di configurazione JSON e gestisce tutti i possibili errori legati ai file e al parsing JSON.

πŸ“‹ Requisiti:

  • Gestire file non trovato (FileNotFoundError)
  • Gestire permessi insufficienti (PermissionError)
  • Gestire JSON malformato (json.JSONDecodeError)
  • Garantire chiusura del file con finally
  • Ritornare un dizionario vuoto in caso di errore
  • Loggare gli errori su console
import json def carica_config(percorso_file): """ Carica configurazione da file JSON. Returns: dict: Configurazione o {} se errore """ file = None try: # Apri e leggi file pass except FileNotFoundError: pass finally: # Chiudi file se aperto pass
🌟 Bonus:
Implementa un sistema di fallback: se il file principale non esiste, prova a caricare "config_default.json"
3
Validatore di Email
MEDIO

Crea una funzione di validazione email che solleva eccezioni personalizzate per diversi tipi di errori di validazione.

πŸ“‹ Requisiti:

  • Creare classe eccezione base: EmailValidationError
  • Creare eccezioni specifiche: InvalidFormatError, MissingAtSymbolError, InvalidDomainError
  • Validare presenza di @
  • Validare formato dominio (es: .com, .it, .org)
  • Validare lunghezza minima parte locale (prima di @)
  • Gestire tutte le eccezioni personalizzate nel chiamante
# Definisci le eccezioni personalizzate class EmailValidationError(Exception): pass class InvalidFormatError(EmailValidationError): pass def valida_email(email): """ Valida formato email. Raises: InvalidFormatError: se formato non valido MissingAtSymbolError: se manca @ InvalidDomainError: se dominio non valido """ if "@" not in email: raise MissingAtSymbolError("Email deve contenere @") # Continua validazione...

πŸ§ͺ Test Cases:

valida_email("user@example.com") β†’ True
valida_email("userexample.com") β†’ MissingAtSymbolError
valida_email("u@ex") β†’ InvalidDomainError
valida_email("@example.com") β†’ InvalidFormatError
4
Convertitore di Lista Sicuro
MEDIO

Scrivi una funzione che converte una lista di stringhe in numeri interi, gestendo le conversioni non valide senza interrompere l'intero processo.

πŸ“‹ Requisiti:

  • Processare tutti gli elementi anche se alcuni falliscono
  • Ritornare tupla: (lista_valori_convertiti, lista_errori)
  • Per ogni errore, salvare: indice, valore originale, tipo errore
  • Usare else per confermare conversioni riuscite
  • Stampare statistiche finali (successi/fallimenti)
def converti_lista(lista_stringhe): """ Converte lista di stringhe in interi. Returns: tuple: (lista_convertiti, lista_errori) """ convertiti = [] errori = [] for indice, valore in enumerate(lista_stringhe): try: # Tentativo conversione pass except ValueError as e: # Salva informazioni errore pass else: # Conversione riuscita pass return convertiti, errori

πŸ§ͺ Test Cases:

Input: ["10", "20", "abc", "30", "xyz"]
Output: ([10, 20, 30], [info_errore_abc, info_errore_xyz])
Statistiche: 3 successi, 2 fallimenti
5
Sistema di Login con Tentativi
MEDIO

Implementa un sistema di login che blocca l'account dopo 3 tentativi falliti, gestendo diverse tipologie di errori.

πŸ“‹ Requisiti:

  • Creare eccezioni: InvalidCredentialsError, AccountLockedError
  • Massimo 3 tentativi di login
  • Validare lunghezza username (min 3 caratteri)
  • Validare lunghezza password (min 6 caratteri)
  • Bloccare account dopo 3 tentativi falliti
  • Usare finally per loggare ogni tentativo
  • Ritornare True se login riuscito, False altrimenti
class InvalidCredentialsError(Exception): pass class AccountLockedError(Exception): pass def login(username, password, tentativi_falliti=0): try: # Verifica se account bloccato if tentativi_falliti >= 3: raise AccountLockedError("Account bloccato") # Validazioni... except InvalidCredentialsError: pass finally: # Log tentativo pass
🌟 Bonus:
Aggiungi un timer di blocco: l'account rimane bloccato per 5 minuti, poi i tentativi vengono resettati.
6
Parser CSV Robusto
DIFFICILE

Crea un parser CSV che gestisce file corrotti, righe malformate e dati mancanti, continuando l'elaborazione e generando un report degli errori.

πŸ“‹ Requisiti:

  • Leggere file CSV con gestione errori I/O
  • Validare numero colonne per ogni riga
  • Gestire righe con dati mancanti
  • Convertire tipi di dati con gestione errori
  • Non interrompere il parsing per righe corrotte
  • Generare report: righe totali, valide, corrotte
  • Salvare righe valide in una lista
  • Salvare dettagli errori in un log
def parse_csv(file_path, expected_columns): """ Parse CSV con gestione errori completa. Returns: dict: { 'data': lista righe valide, 'stats': statistiche, 'errors': lista errori } """ dati = [] errori = [] try: with open(file_path, 'r') as f: for num_riga, riga in enumerate(f, 1): try: # Processa riga pass except Exception as e: # Salva errore ma continua errori.append({ 'riga': num_riga, 'errore': str(e) }) except FileNotFoundError: pass
πŸ’‘ Suggerimento:
Usa try-except annidati: uno esterno per gestire il file, uno interno per gestire ogni singola riga.
7
Gestore Transazioni Bancarie
DIFFICILE

Implementa un sistema di trasferimento denaro con gestione errori, validazioni e rollback in caso di fallimento.

πŸ“‹ Requisiti:

  • Validare importo (positivo, formato corretto)
  • Verificare saldo sufficiente
  • Simulare prelievo da conto A
  • Simulare deposito su conto B
  • Se deposito fallisce, eseguire rollback (ripristina conto A)
  • Logging completo di ogni operazione
  • Eccezioni personalizzate: InsufficientFundsError, InvalidAmountError, TransactionError
  • Usare finally per rilasciare eventuali lock
class InsufficientFundsError(Exception): pass def trasferisci(conto_a, conto_b, importo): """ Trasferisce denaro tra due conti. Args: conto_a: dizionario con saldo conto_b: dizionario con saldo importo: importo da trasferire Raises: InvalidAmountError: se importo non valido InsufficientFundsError: se saldo insufficiente TransactionError: se transazione fallisce """ importo_prelevato = 0 try: # Validazione importo if importo <= 0: raise InvalidAmountError() # Verifica saldo if conto_a['saldo'] < importo: raise InsufficientFundsError() # Preleva da A conto_a['saldo'] -= importo importo_prelevato = importo # Simula possibile errore nel deposito if random.random() < 0.3: raise TransactionError("Errore deposito") # Deposita su B conto_b['saldo'] += importo except TransactionError: # ROLLBACK: ripristina conto A if importo_prelevato > 0: conto_a['saldo'] += importo_prelevato raise finally: # Log operazione pass
8
Decoratore Retry con Backoff
DIFFICILE

Crea un decoratore che riprova automaticamente una funzione in caso di errore, con attesa crescente tra i tentativi (exponential backoff).

πŸ“‹ Requisiti:

  • Parametri: max_tentativi, delay_iniziale, eccezioni_da_catturare
  • Attesa crescente: 1s, 2s, 4s, 8s (exponential backoff)
  • Loggare ogni tentativo con dettagli
  • Dopo max_tentativi, rilanciare ultima eccezione
  • Catturare solo eccezioni specificate
  • Preservare nome e docstring funzione originale
import time from functools import wraps def retry(max_tentativi=3, delay=1, eccezioni=(Exception,)): """ Decoratore per retry automatico con backoff. """ def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for tentativo in range(1, max_tentativi + 1): try: return func(*args, **kwargs) except eccezioni as e: if tentativo == max_tentativi: raise attesa = delay * (2 ** (tentativo - 1)) time.sleep(attesa) return wrapper return decorator # Esempio uso: @retry(max_tentativi=5, delay=1, eccezioni=(ConnectionError,)) def chiama_api(): # Simula chiamata API instabile pass

πŸ§ͺ Test:

Funzione fallisce 2 volte, poi successo:
Tentativo 1: Errore, attesa 1s
Tentativo 2: Errore, attesa 2s
Tentativo 3: Successo βœ“
9
Context Manager per Database
DIFFICILE

Implementa un context manager personalizzato per gestire connessioni database con commit/rollback automatico.

πŸ“‹ Requisiti:

  • Creare classe DatabaseConnection come context manager
  • Implementare __enter__ per aprire connessione
  • Implementare __exit__ per chiusura e gestione errori
  • Se nessun errore: commit automatico
  • Se errore: rollback automatico
  • Garantire chiusura connessione sempre
  • Logging di tutte le operazioni
  • Gestire errori di connessione
class DatabaseConnection: """Context manager per connessioni database.""" def __init__(self, db_name): self.db_name = db_name self.connection = None def __enter__(self): """Apre connessione database.""" try: self.connection = connetti_db(self.db_name) print(f"Connesso a {self.db_name}") return self.connection except ConnectionError as e: print(f"Errore connessione: {e}") raise def __exit__(self, exc_type, exc_value, traceback): """Gestisce chiusura e commit/rollback.""" if self.connection: try: if exc_type is None: # Nessun errore: commit self.connection.commit() print("Commit eseguito") else: # Errore: rollback self.connection.rollback() print(f"Rollback per errore: {exc_value}") finally: self.connection.close() print("Connessione chiusa") # Return False per propagare eccezione return False # Uso: with DatabaseConnection("mydb") as conn: conn.execute("INSERT INTO users...") # Commit automatico se tutto OK
🌟 Bonus:
Aggiungi un parametro auto_commit=False per permettere transazioni manuali.
10
Sistema di Monitoraggio Errori
DIFFICILE

Crea un sistema completo di monitoraggio errori che traccia, analizza e genera report su tutte le eccezioni che si verificano nell'applicazione.

πŸ“‹ Requisiti:

  • Classe ErrorMonitor singleton (unica istanza)
  • Registrare ogni errore con: timestamp, tipo, messaggio, stack trace, contesto
  • Metodi: register_error(), get_statistics(), get_errors_by_type()
  • Calcolare: totale errori, errori per tipo, errore piΓΉ frequente
  • Generare grafici temporali degli errori (opzionale)
  • Esportare report in JSON e HTML
  • Decoratore @monitor_errors per monitorare funzioni
  • Alert se errori superano soglia in un periodo
from collections import defaultdict, Counter from datetime import datetime import traceback import json class ErrorMonitor: """Sistema di monitoraggio errori singleton.""" _instance = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) cls._instance._initialized = False return cls._instance def __init__(self): if self._initialized: return self._initialized = True self.errors = [] self.counter = Counter() self.errors_by_type = defaultdict(list) def register_error(self, exception, context=None): """Registra un errore con tutti i dettagli.""" error_type = type(exception).__name__ error_record = { 'timestamp': datetime.now().isoformat(), 'type': error_type, 'message': str(exception), 'traceback': traceback.format_exc(), 'context': context or {} } self.errors.append(error_record) self.counter[error_type] += 1 self.errors_by_type[error_type].append(error_record) # Verifica soglia alert self._check_alert_threshold() def get_statistics(self): """Genera statistiche complete.""" return { 'total_errors': len(self.errors), 'errors_by_type': dict(self.counter), 'most_common': self.counter.most_common(5), 'last_error': self.errors[-1] if self.errors else None } def export_report(self, format='json'): """Esporta report in formato specificato.""" if format == 'json': return json.dumps({ 'statistics': self.get_statistics(), 'all_errors': self.errors }, indent=2) elif format == 'html': # Genera report HTML pass def _check_alert_threshold(self): """Verifica se errori superano soglia.""" pass # Decoratore per monitorare funzioni def monitor_errors(func): def wrapper(*args, **kwargs): monitor = ErrorMonitor() try: return func(*args, **kwargs) except Exception as e: monitor.register_error(e, context={ 'function': func.__name__, 'args': args, 'kwargs': kwargs }) raise return wrapper # Uso: @monitor_errors def funzione_rischiosa(): pass

πŸ§ͺ Esempio Output:

Statistiche:
- Totale errori: 45
- ValueError: 20
- ConnectionError: 15
- TypeError: 10
- Errore piΓΉ frequente: ValueError (44%)
- Ultimo errore: ValueError alle 14:35:22
🌟 Bonus Avanzati:
  • Integrazione con email per alert automatici
  • Dashboard web per visualizzare statistiche real-time
  • Raggruppamento errori per time window (es: errori per ora)
  • Confronto con periodi precedenti
  • Predizione di possibili problemi basata su trend

πŸŽ“ Complimenti!

Hai completato tutti i 10 esercizi sulla gestione degli errori in Python!

Questi esercizi coprono tutti gli aspetti fondamentali e avanzati:
Try-Except-Else-Finally, Eccezioni Personalizzate, Context Manager,
Decoratori, Logging, Monitoraggio e Best Practices

πŸ“Š Criteri di Valutazione Suggeriti

Per Esercizi Facili (1-2):

  • Gestione corretta delle eccezioni base (40%)
  • Uso appropriato di try-except-finally (30%)
  • Messaggi di errore chiari (20%)
  • Codice pulito e commentato (10%)

Per Esercizi Medi (3-5):

  • Eccezioni personalizzate ben strutturate (30%)
  • Gestione completa di tutti i casi edge (30%)
  • Uso corretto di else e finally (20%)
  • Logging e documentazione (20%)

Per Esercizi Difficili (6-10):

  • Architettura e design pattern appropriati (25%)
  • Gestione errori complessa e rollback (25%)
  • Robustezza e gestione casi limite (25%)
  • Codice professionale e manutenibile (15%)
  • Implementazione bonus (10%)

πŸ’‘ Suggerimenti per l'Implementazione

  1. Inizia dai facili: Costruisci confidenza con esercizi 1-2
  2. Testa sempre: Scrivi test per ogni caso (successo ed errore)
  3. Leggi gli errori: I traceback Python sono molto informativi
  4. Usa il debugger: pdb o IDE debugger per capire il flusso
  5. Refactoring: Prima fai funzionare, poi migliora il codice
  6. Documenta: Commenta le parti complesse e scrivi docstring
  7. Chiedi aiuto: Consulta la documentazione ufficiale Python
  8. Confronta: Studia soluzioni di progetti open source

πŸ“š Risorse per Approfondire

  • Documentazione Python: docs.python.org/3/tutorial/errors.html
  • Real Python: Articoli approfonditi su exception handling
  • PEP 8: Convenzioni di stile per il codice Python
  • Stack Overflow: Cerca errori specifici e soluzioni
  • GitHub: Studia codice di progetti famosi (Django, Flask, Requests)
  • Python Testing: pytest per scrivere test delle eccezioni

πŸš€ Dopo Questi Esercizi

Una volta completati tutti gli esercizi, sarai pronto per:

  • βœ… Scrivere codice Python robusto e affidabile
  • βœ… Gestire errori in applicazioni reali
  • βœ… Creare sistemi di logging professionali
  • βœ… Implementare pattern avanzati come retry e circuit breaker
  • βœ… Contribuire a progetti open source con confidenza
  • βœ… Affrontare colloqui tecnici su exception handling

Buon lavoro e... che i tuoi try siano sempre gestiti! 🐍✨